
/* GCSx
** ANIMGROUPEDIT.CPP
**
** Animation group support
** Expands basic AnimGroup to include editor functionality
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

AnimGroupEdit::AnimGroupEdit(WorldEdit* myWorld, int myId) : AnimGroup(myWorld, myId) { start_func
    assert(myWorld);

    // Find an unused "Untitled" name
    if (myId) {
        string findName("animations");
        int pos = 1;
        while (myWorld->findAnimGroup(findName)) {
            findName = "animations ";
            findName += intToStr(++pos);
        }
    
        findName[0] = toupper(findName[0]);
        name = findName;
        headerModified = contentModified = 1;
    }
    else headerModified = contentModified = 0;

    nameL = name;
    toLower(nameL);

    browserNode = NULL;
    disassociated = 0;
    desktop->broadcastObjChange(OBJ_ANIMGROUP | OBJMOD_CREATE, this, 0, 0);
}

AnimGroupEdit::~AnimGroupEdit() { start_func
    // @TODO: doesn't appear to be needed; no way to delete accidentally
    // if (!disassociated) desktop->broadcastObjChange(OBJ_ANIMGROUP | OBJMOD_DELETE, this, 0, 0);

    // World should be the one deleting us, which will delete our node also
}

void AnimGroupEdit::disassociate() throw_File { start_func
    // Ensure not cached- our exception point
    cacheLoad();
    
    // Drop browser node
    dropBrowser();
    
    // Notify objects
    desktop->broadcastObjChange(OBJ_ANIMGROUP | OBJMOD_DELETE, this, 0, 0);
    
    disassociated = 1;
}

void AnimGroupEdit::reassociate(TreeView* node) { start_func
    assert(disassociated);
    
    // Notify objects
    desktop->broadcastObjChange(OBJ_ANIMGROUP | OBJMOD_CREATE, this, 0, 0);
    
    // Browser node
    addToBrowser(node, 0);

    // Ensure written to file
    headerModified = contentModified = 1;
    disassociated = 0;
}

void AnimGroupEdit::setHeaderModified() { start_func
    if (!headerModified) headerModified = 1;
}

void AnimGroupEdit::setContentModified() { start_func
    if (!contentModified) {
        contentModified = 1;
        // No possibility of returning to cache
        delete cacheFile;
        cacheFile = NULL;
    }
}

void AnimGroupEdit::dropBrowser() { start_func
    browserNode = NULL;
}

void AnimGroupEdit::addToBrowser(TreeView* node, int makeCurrent) { start_func
    browserNode = new TreeView(name, this, 0, treeviewEventWrap);
    browserNode->setIcon(7);
    node->insert(browserNode, makeCurrent);
}

int AnimGroupEdit::treeviewEvent(int code, int command, int check) { start_func
    if (check) {
        if (command == EDIT_DELETE) return Window::COMMAND_ENABLE;
        return Window::COMMAND_HIDE;
    }

    switch (command) {
        case LV_SELECT:
            openBrowseWindow();
            return 1;
            
        case LV_RCLICK:
            // @TODO: Should actually be a popup with this as an item
            propertiesDialog(desktop->findPreviousFocusWindow());
            return 1;
        
        case EDIT_DELETE:
        case LV_DELETE:
            // Handles any error conditions/cancellation for us
            getWorldEdit()->deleteAnimGroup(this, desktop->findPreviousFocusWindow());
            return 1;
    }
    
    return 0;
}

int AnimGroupEdit::treeviewEventWrap(void* ptr, int code, int command, int check) { start_func
    return ((AnimGroupEdit*)ptr)->treeviewEvent(code, command, check);
}

void AnimGroupEdit::setSize(int newCount, Window* srcWin, Window* exWin) throw_Undo { start_func
    // @TODO: Undo (already in throw def), update events
    if (newCount != numAnim) {
        setHeaderModified();
        setContentModified();
        AnimSequence* newAnims = NULL;
        if (newCount) {
            newAnims = new AnimSequence[newCount];
            // Copy existing
            for (int pos = min(numAnim, newCount) - 1; pos >= 0; --pos) {
                newAnims[pos] = anims[pos];
            }
            // (any empty spaces initialized already)
        }
        delete[] anims;
        anims = newAnims;
        numAnim = newCount;
    }
}

void AnimGroupEdit::setName(const string& newName, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (name != newName) {
        setHeaderModified();
        if (world) {
            getWorldEdit()->undo.storeUndoName(UndoBuffer::UNDO_ANIMGROUPNAME, id, name, newName, srcWin);
            world->deindexAnimGroup(this);
        }
        name = newName;
        nameL = newName;
        toLower(nameL);
        if (world) world->indexAnimGroup(this);
        if (browserNode) browserNode->changeName(newName);
    
        desktop->broadcastObjChange(OBJ_TILESET | OBJMOD_NAME, this, 0, 0, exWin);
    }
}

void AnimGroupEdit::openEditWindow(int subid) { start_func
    try {
        if (!numAnim) return;
        if (subid < 1) subid = 1;
        else if (subid > numAnim) subid = numAnim;
        new AnimGroupPaint(this, subid); // Exception point
    }
    catch (FileException& e) {
        guiErrorBox(string(e.details), errorTitleFile);
    }
}

void AnimGroupEdit::openBrowseWindow() { start_func
    try {
        //@TODO: new window
        //@TODO: add anim requires at least one tileset
    }
    catch (FileException& e) {
        guiErrorBox(string(e.details), errorTitleFile);
    }
}

void AnimGroupEdit::loadAnim(int id, AnimSequence* copyAnim) { start_func
    assert(id > 0);
    assert(id <= numAnim);
    
    *copyAnim = anims[id - 1];
}

void AnimGroupEdit::saveAnim(int id, const AnimSequence* newAnim, Window* exWin) { start_func
    assert(id > 0);
    assert(id <= numAnim);
    
    anims[id - 1] = *newAnim;
    desktop->broadcastObjChange(OBJ_ANIMGROUP | OBJMOD_ANIM, this, id, 0, exWin);
}

void AnimGroupEdit::loadAnimName(int id, string* copyName) { start_func
    assert(id > 0);
    assert(id <= numAnim);
    
    *copyName = anims[id - 1].name;
}

void AnimGroupEdit::saveAnimName(int id, const string* newName, Window* exWin) { start_func
    assert(id > 0);
    assert(id <= numAnim);

    if (anims[id - 1].name != *newName) {
        anims[id - 1].name = *newName;
        desktop->broadcastObjChange(OBJ_ANIMGROUP | OBJMOD_NAME, this, id - 1, 0, exWin);
    }
}

int AnimGroupEdit::propertiesDialog(Window* srcWin, Window* exWin) { start_func
    string newName = name;

    if (AnimGroupPropertiesDialog::create()->run(&newName, this)) {
        try {
            markLock();
        }
        catch (FileException& e) {
            guiErrorBox(string(e.details), errorTitleFile);
            return 0;
        }
        try {
            getWorldEdit()->undo.preUndoBlock();
            setName(newName, srcWin, exWin);
            getWorldEdit()->undo.postUndoBlock();
        }
        catch (UndoException& e) {
            markUnlock();
            return 0;
        }
        markUnlock();
        return 1;
    }
    
    return 0;
}

int AnimGroupEdit::isHeaderModified() const { start_func
    return headerModified;
}

int AnimGroupEdit::isContentModified() const { start_func
    return contentModified;
}

Uint32 AnimGroupEdit::saveHeader(FileWrite* file) throw_File { start_func
    file->writeStr(name);
    file->writeInt(id);
    file->writeInt(defWidth);
    file->writeInt(defHeight);
    file->writeInt(numAnim);
    
    return 1;
}

void AnimGroupEdit::saveContent(FileWrite* file) throw_File { start_func
    // (our data shouldn't be empty unless we have none
    assert(anims || !numAnim);

    if (numAnim) {
        for (int pos = 0; pos < numAnim; ++pos) {
            file->writeStr(anims[pos].name);
            file->writeInt(anims[pos].count);
            file->writeInt(anims[pos].width);
            file->writeInt(anims[pos].height);
            file->writeInt8(anims[pos].loopType);
            
            // Frames
            for (int subpos = 0; subpos < anims[pos].count; ++subpos) {
                file->writeInt(anims[pos].frames[subpos].count);
                file->writeInt16(anims[pos].frames[subpos].xOrigin);
                file->writeInt16(anims[pos].frames[subpos].yOrigin);
                file->writeInt16(anims[pos].frames[subpos].xOffs);
                file->writeInt16(anims[pos].frames[subpos].yOffs);
                file->writeInt8(anims[pos].frames[subpos].delay);
                
                // Components
                for (int subsubpos = 0; subsubpos < anims[pos].frames[subpos].count; ++subsubpos) {
                    file->writeInt(anims[pos].frames[subpos].comps[subsubpos].tileset->getId());
                    file->writeInt16(anims[pos].frames[subpos].comps[subsubpos].index);
                    file->writeInt16(anims[pos].frames[subpos].comps[subsubpos].x);
                    file->writeInt16(anims[pos].frames[subpos].comps[subsubpos].y);
                    file->writeInt8(anims[pos].frames[subpos].comps[subsubpos].r);
                    file->writeInt8(anims[pos].frames[subpos].comps[subsubpos].g);
                    file->writeInt8(anims[pos].frames[subpos].comps[subsubpos].b);
                    file->writeInt8(anims[pos].frames[subpos].comps[subsubpos].alpha);
                    file->writeInt8(anims[pos].frames[subpos].comps[subsubpos].effects);
                }
            }
        }
    }
}

void AnimGroupEdit::saveSuccess() { start_func
    headerModified = 0;
    contentModified = 0;
}

void AnimGroupEdit::cachedContent(FileRead* file, int oldData) { start_func
    // If already cached..
    if (cached) {
        delete cacheFile;
        cacheFile = file;
    }
    else if (oldData) {
        delete file;
    }
    else {
        // Cache ourselves?
        if (!lockCount) {
            delete cacheFile;
            cacheFile = file;
            delete[] anims;
            anims = NULL;
            cached = 1;
        }
        else {
            delete file;
        }
    }
}

